/**
 * Copyright (C) 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.inject.spi;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement;

import java.util.List;

/**
 * Contains information about where and how an {@link Element element} was
 * bound.
 * <p>
 * The {@link #getDeclaringSource() declaring source} refers to a location in
 * source code that defines the Guice {@link Element element}. For example, if
 * the element is created from a method annotated by {@literal @Provides}, the
 * declaring source of element would be the method itself.
 * <p>
 * The {@link #getStackTrace()} refers to the sequence of calls ends at one of
 * {@link com.google.inject.Binder} {@code bindXXX()} methods and eventually
 * defines the element. Note that {@link #getStackTrace()} lists
 * {@link StackTraceElement StackTraceElements} in reverse chronological order.
 * The first element (index zero) is the last method call and the last element
 * is the first method invocation. By default, the stack trace is not collected.
 * The default behavior can be changed by setting the
 * {@code guice_include_stack_traces} flag value. The value can be either
 * {@code OFF}, {@code ONLY_FOR_DECLARING_SOURCE} or {@code COMPLETE}. Note that
 * collecting stack traces for every binding can cause a performance hit when
 * the injector is created.
 * <p>
 * The sequence of class names of {@link com.google.inject.Module modules}
 * involved in the element creation can be retrieved by
 * {@link #getModuleClassNames()}. Similar to {@link #getStackTrace()}, the
 * order is reverse chronological. The first module (index 0) is the module that
 * installs the {@link Element element}. The last module is the root module.
 * <p>
 * In order to support the cases where a Guice {@link Element element} is
 * created from another Guice {@link Element element} (original) (e.g., by
 * {@link Element#applyTo}), it also provides a reference to the original
 * element source ({@link #getOriginalElementSource()}).
 *
 * @since 4.0
 */
public final class ElementSource {

    /** 
     * The {@link ElementSource source} of element that this element created from (if there is any),
     * otherwise {@code null}.
     */
    final ElementSource originalElementSource;

    /** The {@link ModuleSource source} of module creates the element. */
    final ModuleSource moduleSource;

    /** 
     * The partial call stack that starts at the last module {@link Module#Configure(Binder)
     * configure(Binder)} call. The value is empty if stack trace collection is off.
     */
    final InMemoryStackTraceElement[] partialCallStack;

    /** 
     * Refers to a single location in source code that causes the element creation. It can be any 
     * object such as {@link Constructor}, {@link Method}, {@link Field}, {@link StackTraceElement}, 
     * etc. For example, if the element is created from a method annotated by {@literal @Provides}, 
     * the declaring source of element would be the method itself.
     */
    final Object declaringSource;

    /**
     * Creates a new {@ElementSource} from the given parameters. 
     * @param originalElementSource The source of element that this element created from (if there is
     * any), otherwise {@code null}.
     * @param declaringSource the source (in)directly declared the element.
     * @param moduleSource the moduleSource when the element is bound
     * @param partialCallStack the partial call stack from the top module to where the element is 
     * bound
     */
    ElementSource(/* @Nullable */ ElementSource originalSource, Object declaringSource, ModuleSource moduleSource, StackTraceElement[] partialCallStack) {
        Preconditions.checkNotNull(declaringSource, "declaringSource cannot be null.");
        Preconditions.checkNotNull(moduleSource, "moduleSource cannot be null.");
        Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null.");
        this.originalElementSource = originalSource;
        this.declaringSource = declaringSource;
        this.moduleSource = moduleSource;
        this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack);
    }

    /**
     * Returns the {@link ElementSource} of the element this was created or copied from. If this was
     * not created or copied from another element, returns {@code null}.
     */
    public ElementSource getOriginalElementSource() {
        return originalElementSource;
    }

    /**
     * Returns a single location in source code that defines the element. It can be any object
     * such as {@link java.lang.reflect.Constructor}, {@link java.lang.reflect.Method},
     * {@link java.lang.reflect.Field}, {@link StackTraceElement}, etc. For
     * example, if the element is created from a method annotated by {@literal @Provides}, the
     * declaring source of element would be the method itself.
     */
    public Object getDeclaringSource() {
        return declaringSource;
    }

    /**
     * Returns the class names of modules involved in creating this {@link Element}. The first
     * element (index 0) is the class name of module that defined the element, and the last element
     * is the class name of root module.
     */
    public List<String> getModuleClassNames() {
        return moduleSource.getModuleClassNames();
    }

    /**
     * Returns the position of {@link com.google.inject.Module#configure configure(Binder)} method
     * call in the {@link #getStackTrace stack trace} for modules that their classes returned by
     * {@link #getModuleClassNames}. For example, if the stack trace looks like the following:
     * <p>
     * {@code
     *  0 - Binder.bind(),
     *  1 - ModuleTwo.configure(),
     *  2 - Binder.install(),
     *  3 - ModuleOne.configure(),
     *  4 - theRest(). 
     * }
     * <p>
     * 1 and 3 are returned.
     * <p>
     * In the cases where stack trace is not available (i.e., the stack trace was not collected),
     * it returns -1 for all module positions.
     */
    public List<Integer> getModuleConfigurePositionsInStackTrace() {
        int size = moduleSource.size();
        Integer[] positions = new Integer[size];
        int chunkSize = partialCallStack.length;
        positions[0] = chunkSize - 1;
        ModuleSource current = moduleSource;
        for (int cursor = 1; cursor < size; cursor++) {
            chunkSize = current.getPartialCallStackSize();
            positions[cursor] = positions[cursor - 1] + chunkSize;
            current = current.getParent();
        }
        return ImmutableList.<Integer> copyOf(positions);
    }

    /**
     * Returns the sequence of method calls that ends at one of {@link com.google.inject.Binder}
     * {@code bindXXX()} methods and eventually defines the element. Note that
     * {@link #getStackTrace} lists {@link StackTraceElement StackTraceElements} in reverse
     * chronological order. The first element (index zero) is the last method call and the last
     * element is the first method invocation. In the cases where stack trace is not available
     * (i.e.,the stack trace was not collected), it returns an empty array.
     */
    public StackTraceElement[] getStackTrace() {
        int modulesCallStackSize = moduleSource.getStackTraceSize();
        int chunkSize = partialCallStack.length;
        int size = moduleSource.getStackTraceSize() + chunkSize;
        StackTraceElement[] callStack = new StackTraceElement[size];
        System.arraycopy(StackTraceElements.convertToStackTraceElement(partialCallStack), 0, callStack, 0, chunkSize);
        System.arraycopy(moduleSource.getStackTrace(), 0, callStack, chunkSize, modulesCallStackSize);
        return callStack;
    }

    /**
     * Returns {@code getDeclaringSource().toString()} value.
     */
    @Override
    public String toString() {
        return getDeclaringSource().toString();
    }
}
